package com.nulldev.util.sys.process;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.java.ProcessUtil;
import com.nulldev.util.java.ProcessUtil.ProcessUtilRead;
import com.nulldev.util.java.swing.SwingUtil;
import com.nulldev.util.java.swing.extSwing.eActiveConsoledBar;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.os.OSUtil;
import com.nulldev.util.os.OSUtil.OSType;
import com.nulldev.util.os.OSUtil.PackageUtil;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.sys.process.Process.ProcessType;
import com.nulldev.util.threading.legacy.ThreadExecutor;

public class ProcessManager {

	private static boolean forceInstallModules = JVMUtil.isArg("--processMan.forceInstallModules");
	public static boolean reduceCPUCalls;
	private static final List<Process> PROCESSES = new ArrayList<Process>();
	private static boolean isParsing;
	private static final Logger log = LoggerFactory.getLogger();
	public static final Process BLANK = new Process(-1, "N/A", ProcessType.UNKNOWN);
	
	public static List<Process> parse() {
		if (isParsing) {
			log.error("Already parsing!");
			return PROCESSES;
		}
		isParsing = true;
		if (OSUtil.getOperatingSystemType() == OSType.Windows) {
			try {
				final ArrayList<Process> process = new ArrayList<Process>();
				
				final String result = ProcessUtil.execCmd("tasklist /v /FO:CSV");
				final String[] lines = StringsUtil.byLineSepeartor(result);
				for (int i = 1; i < lines.length; i++) {
					final String line = lines[i];
					//final String[] data = line.split(",");
					final String[] data = StringsUtil.safeSplit(line).split("__sp__");
					
					ProcessType ty = ProcessType.UNKNOWN;
					final boolean isConsole = StringsUtil.isEqualCM(data, 2, "Console");
					final boolean isService = StringsUtil.isEqualCM(data, 2, "Services");
					if (isConsole) {
						ty = ProcessType.Console;
					}else if (isService) {
						ty = ProcessType.Service;
					}else {
						ty = ProcessType.Process;
					}
					
					final Process p = new Process(Long.parseLong(data[1].replaceAll("\"", "")), data[0].replaceAll("\"", ""), ty);
					p.setTitle(data[data.length - 1].replaceAll("\"", ""));
					process.add(p);
				}
				
				PROCESSES.clear();
				PROCESSES.addAll(process);
				isParsing = false;
				return process;
			}catch (Exception e) {
				e.printStackTrace();
			}
		}else if (OSUtil.getOperatingSystemType() == OSType.Linux) {
			try {
				final ArrayList<Process> process = new ArrayList<Process>();
				
				final String result = ProcessUtil.execCmd("ps aux").replaceAll("Program Files", "%pf");
				final String[] data = StringsUtil.limitSpaces(result, 1).split(JVMUtil.lineSep());
				data[0] = "";
				for (final String lineX : data) {
					final String[] line = lineX.split(" ");
					final String proc = ArrayUtils.StringArrayUtils.at(line, 10, "N/A").replaceAll(Pattern.quote("%pf"), "");
					final Process pro = new Process(MathUtil.safeParseList(line, 1, -1), proc, ProcessType.Process);
					pro.setMemoryUsage(MathUtil.safeParseList(line, 4, -1));
					pro.setArgs(ArrayUtils.StringArrayUtils.addFrom(line, 11));
					process.add(pro);
				}
				PROCESSES.clear();
				PROCESSES.addAll(process);
				boolean fetched = true;
				if (OSUtil.PackageUtil.isPackage("wmctrl")) {
					final String pre_out = ProcessUtil.execCmd("wmctrl -lp -i");
					final String out = StringsUtil.limitSpaces(pre_out, 1);
					final String[] dta = out.split(JVMUtil.lineSep());
					for (final String lt : dta) {
						final String[] vals = lt.split(" ");
						final long pid = MathUtil.safeParseList(vals, 2, -1);
						if (pid > 0) {
							final Process px = find(pid);
							px.setTitle(ArrayUtils.StringArrayUtils.addFrom(vals, 4));
						}
					}
					fetched = true;
				}
				if (!reduceCPUCalls || !fetched) {
					if (OSUtil.PackageUtil.isPackage("xprop")) {
						try {
							final ProcessBuilder cmd = new ProcessBuilder("/bin/sh", "-c", "xprop -root | grep \"_NET_CLIENT_LIST_STACKING(WINDOW)\"");
							final FinalBypass<String> pre_out = new FinalBypass<String>("");
							ProcessUtil.ProcessUtilActive.execActiveCmd_NoQueue_UseProcessBuilder(cmd, new ProcessUtilRead() {
								
								@Override
								public void onRead(String input) {
									pre_out.setObject(pre_out.getObject() + input);
								}
							});
							final String p_f = pre_out.getObject().replaceAll(Pattern.quote("_NET_CLIENT_LIST_STACKING(WINDOW): window id # "), "");
							final String[] vals = p_f.split(", ");
							for (final String val : vals) {
								final ProcessBuilder cmda = new ProcessBuilder("/bin/sh", "-c", "xprop -id " + val + " | grep _NET_WM_PID");
								final FinalBypass<String> p = new FinalBypass<String>("");
								ProcessUtil.ProcessUtilActive.execActiveCmd_NoQueue_UseProcessBuilder(cmda, new ProcessUtilRead() {
									
									@Override
									public void onRead(String input) {
										p.setObject(p.getObject() + input);
									}
								});
								final String PID = p.getObject().replaceAll(Pattern.quote("_NET_WM_PID(CARDINAL) = "), "");
								final ProcessBuilder cmdb = new ProcessBuilder("/bin/sh", "-c", "xprop -id " + val + " | grep \"_NET_WM_NAME(UTF8_STRING)\"");
								final FinalBypass<String> p1 = new FinalBypass<String>("");
								ProcessUtil.ProcessUtilActive.execActiveCmd_NoQueue_UseProcessBuilder(cmdb, new ProcessUtilRead() {
									
									@Override
									public void onRead(String input) {
										p1.setObject(p1.getObject() + input);
									}
								});
								final String title = p1.getObject().replaceAll(Pattern.quote("_NET_WM_NAME(UTF8_STRING) = "), "");
								if (PID != null && PID.length() > 0 && !PID.equals("")) {
									final Process px = find(Long.parseLong(PID));
									if (!px.isBlank()) {
										try {
											px.setTitle(title.substring(1, title.length() - 1));
										}catch (Exception e) {
											px.setTitle(title);
										}
									}
								}
							}
						}catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				isParsing = false;
				return process;
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
		System.err.println("[ProcessManager.parse()] Unsupported OS!");
		isParsing = false;
		return new ArrayList<Process>();
		
// TODO: Old code
//UnicodeUtils.println(ProcessUtil.execCmd("tasklist.exe"));
//		final String[] sp = ProcessUtil.execCmd("tasklist.exe /nh").replaceAll(",", "").split("\\s+");
//		final String[][] pre_output = ArrayUtils.byChar(sp, "K");
//		final String[][] output = ArrayUtils.clearBlanksS(pre_output);
// process def		
		
//		for (int i = 0; i < output.length; i++) {
//		try {
//			final String[] f = output[i];
//			ProcessType ty = ProcessType.UNKNOWN;
//			if (!f[0].equals("System")) {
//				final boolean isConsole = StringsUtil.isEqual(f, 2, "Console");
//				final boolean isService = StringsUtil.isEqual(f, 2, "Services");
//				if (isConsole) {
//					ty = ProcessType.Console;
//				}else if (isService) {
//					ty = ProcessType.Service;
//				}else {
//					ty = ProcessType.Process;
//				}
//				final Process p = new Process(Long.parseLong(f[1]), f[0], ty);
//				p.setMemoryUsage(Long.parseLong(f[3]));
//				process.add(p);
//			}
//		}catch (Exception e) {
//		}
//
//	}
//
//	PROCESSES.clear();
//	PROCESSES.addAll(process);
//	return process;
	}

	public static List<Process> findAll(final String name) {
		if (PROCESSES.isEmpty()) {
			parse();
		}
		final ArrayList<Process> out = new ArrayList<Process>();
		for (final Process process : PROCESSES) {
			if (process.getExecutable().equals(name)) {
				out.add(process);
			}
		}
		return out;
	}
	
	public static void promptAndInstallModules() {
		if (OSUtil.getOperatingSystemType() == OSType.Linux) {
			if (!SwingUtil.isX11Safe()) {
				return;
			}
			if ((forceInstallModules ? true : !OSUtil.PackageUtil.isPackage("wmctrl")) && !OSUtil.PackageUtil.isPackage("xprop") && !OSUtil.PackageUtil.getPackageManager().equals("N/A")) {
//				try {
//					UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//				}catch (Exception e) {}
				final int choice = JOptionPane.showConfirmDialog(null, "Would you like to install a required module [wmctrl] that allows title detection?", "nullUtil: Process Util Prompt", JOptionPane.YES_NO_OPTION);
				if (choice == JOptionPane.YES_OPTION) {
					final eActiveConsoledBar pr = eActiveConsoledBar.main();
					pr.setIndeterminate(true);
					pr.setTitle("nullUtil: Module Installer");
					pr.setText("Installing 'wmctrl'...");
					PackageUtil.installModuleActive("wmctrl", new ProcessUtilRead() {
						
						@Override
						public void onRead(final String input) {
							pr.appendConsole(input);
						}
					});
					pr.setText("Finishing up...");
					ThreadExecutor.execute(new Runnable() {
						
						@Override
						public void run() {
							pr.setIndeterminate(false);
							pr.setStatus(100);
							pr.setText("Closing in 15 seconds...");
							try {
								Thread.sleep(15000L);
							} catch (InterruptedException e) {
							}
							pr.end();
						}
					});
					return;
				}
				return;
			}
		}
	}
	
	public static void updateTitles() {
		if (PROCESSES.isEmpty()) {
			log.error("No processes found! Run parse() first!");
			return;
		}
		if (OSUtil.getOperatingSystemType() != OSType.Linux) {
			log.error("Unsupported feature! This function requires Linux.");
			return;
		}
		if (!OSUtil.PackageUtil.isPackage("wmctrl") && !OSUtil.PackageUtil.isPackage("xprop")) {
			log.error("Couldn't find package 'wmctrl' or 'xprop'. [Ubuntu users can install this package using 'apt-get'.]");
			return;
		}
		boolean fetched = false;
		if (OSUtil.PackageUtil.isPackage("wmctrl")) {
			try {
				final String pre_out = ProcessUtil.execCmd("wmctrl -lp -i");
				final String out = StringsUtil.limitSpaces(pre_out, 1);
				final String[] dta = out.split(JVMUtil.lineSep());
				for (final String lt : dta) {
					final String[] vals = lt.split(" ");
					final long pid = MathUtil.safeParseList(vals, 2, -1);
					if (pid > 0) {
						final Process px = find(pid);
						px.setTitle(ArrayUtils.StringArrayUtils.addFrom(vals, 4));
					}
				}
				fetched = true;
			}catch (Exception e) {}
		}
		if (!reduceCPUCalls || !fetched) {
			try {
				final ProcessBuilder cmd = new ProcessBuilder("/bin/sh", "-c", "xprop -root | grep \"_NET_CLIENT_LIST_STACKING(WINDOW)\"");
				final FinalBypass<String> pre_out = new FinalBypass<String>("");
				ProcessUtil.ProcessUtilActive.execActiveCmd_NoQueue_UseProcessBuilder(cmd, new ProcessUtilRead() {
					
					@Override
					public void onRead(String input) {
						pre_out.setObject(pre_out.getObject() + input);
					}
				});
				final String p_f = pre_out.getObject().replaceAll(Pattern.quote("_NET_CLIENT_LIST_STACKING(WINDOW): window id # "), "");
				final String[] vals = p_f.split(", ");
				for (final String val : vals) {
					//System.out.println(val);
					final ProcessBuilder cmda = new ProcessBuilder("/bin/sh", "-c", "xprop -id " + val + " | grep _NET_WM_PID");
					final FinalBypass<String> p = new FinalBypass<String>("");
					ProcessUtil.ProcessUtilActive.execActiveCmd_NoQueue_UseProcessBuilder(cmda, new ProcessUtilRead() {
						
						@Override
						public void onRead(String input) {
							p.setObject(p.getObject() + input);
						}
					});
					final String PID = p.getObject().replaceAll(Pattern.quote("_NET_WM_PID(CARDINAL) = "), "");
					final ProcessBuilder cmdb = new ProcessBuilder("/bin/sh", "-c", "xprop -id " + val + " | grep \"_NET_WM_NAME(UTF8_STRING)\"");
					final FinalBypass<String> p1 = new FinalBypass<String>("");
					ProcessUtil.ProcessUtilActive.execActiveCmd_NoQueue_UseProcessBuilder(cmdb, new ProcessUtilRead() {
						
						@Override
						public void onRead(String input) {
							p1.setObject(p1.getObject() + input);
						}
					});
					final String title = p1.getObject().replaceAll(Pattern.quote("_NET_WM_NAME(UTF8_STRING) = "), "");
					if (PID != null && PID.length() > 0 && !PID.equals("")) {
						final Process px = find(Long.parseLong(PID));
						if (!px.isBlank()) {
							try {
								px.setTitle(title.substring(1, title.length() - 1));
							}catch (Exception e) {
								px.setTitle(title);
							}
						}
					}
				}
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	public static Process find(final String name) {
		if (PROCESSES.isEmpty()) {
			parse();
		}
		for (final Process process : PROCESSES) {
			if (process.getExecutable().equals(name)) {
				return process;
			}
		}
		return ProcessManager.BLANK;		
	}
	
	public static Process findE(final String name) {
		if (PROCESSES.isEmpty()) {
			parse();
		}
		for (final Process process : PROCESSES) {
			if (process.getExecutable().equals(name) || process.getExecutable().endsWith(name)) {
				return process;
			}
		}
		return ProcessManager.BLANK;		
	}
	
	public static Process find(final long pid) {
		if (PROCESSES.isEmpty()) {
			parse();
		}
		for (final Process process : PROCESSES) {
			if (process.pid() == pid) {
				return process;
			}
		}
		return ProcessManager.BLANK;		
	}
	
	private static final LinkedList<String> parse_names = new LinkedList<String>();
	public static Process parseName(final String name) {
		if (parse_names.isEmpty()) {
			return parseName_NoWait(name);
		}else {
			parse_names.add(name);
			while (!parse_names.getFirst().equals(name)) {
				try {
					Thread.sleep(50L);
				} catch (InterruptedException e) {
				}
			}
			final Process out = parseName_NoWait(name);
			parse_names.removeFirst();
			return out;
		}
	}
	
	public static Process parseName_NoWait(final String name) {
		if (OSUtil.getOperatingSystemType() == OSType.Windows) {
			final String cmd = "tasklist /v /FO:CSV /fi \"imagename eq " + name + "\"";
			try {
				final ArrayList<Process> process = new ArrayList<Process>();
				
				final String result = ProcessUtil.execCmd(cmd);
				final String[] lines = StringsUtil.byLineSepeartor(result);
				for (int i = 1; i < lines.length; i++) {
					final String line = lines[i];
					final String[] data = StringsUtil.safeSplit(line).split("__sp__");//line.split(",");
					
					ProcessType ty = ProcessType.UNKNOWN;
					final boolean isConsole = StringsUtil.isEqualCM(data, 2, "Console");
					final boolean isService = StringsUtil.isEqualCM(data, 2, "Services");
					if (isConsole) {
						ty = ProcessType.Console;
					}else if (isService) {
						ty = ProcessType.Service;
					}else {
						ty = ProcessType.Process;
					}
					
					final Process p = new Process(Long.parseLong(data[1].replaceAll("\"", "")), data[0].replaceAll("\"", ""), ty);
					p.setTitle(data[data.length - 1].replaceAll("\"", ""));
					process.add(p);
				}
				
				if (!process.isEmpty()) {
					return process.get(0);
				}else {
					return BLANK;
				}
			}catch (Exception e) {
				e.printStackTrace();
			}
		}else if (OSUtil.getOperatingSystemType() == OSType.Linux) {
			try {
				Process outX = BLANK;
				
				final String result = ProcessUtil.execCmd("ps aux");
				final String[] data = StringsUtil.limitSpaces(result, 1).split(JVMUtil.lineSep());
				data[0] = "";
				for (final String lineX : data) {
					final String[] line = lineX.split(" ");
					final String proc = ArrayUtils.StringArrayUtils.at(line, 10, "N/A");
					final Process pro = new Process(MathUtil.safeParseList(line, 1, -1), proc, ProcessType.Process);
					pro.setMemoryUsage(MathUtil.safeParseList(line, 4, -1));
					pro.setArgs(ArrayUtils.StringArrayUtils.addFrom(line, 11));
					outX = pro;
				}
				if (OSUtil.PackageUtil.isPackage("wmctrl")) {
					final String pre_out = ProcessUtil.execCmd("wmctrl -lp -i");
					final String out = StringsUtil.limitSpaces(pre_out, 1);
					final String[] dta = out.split(JVMUtil.lineSep());
					for (final String lt : dta) {
						final String[] vals = lt.split(" ");
						final long pid = MathUtil.safeParseList(vals, 2, -1);
						if (pid > 0 && pid == outX.pid() && outX != BLANK) {
							outX.setTitle(ArrayUtils.StringArrayUtils.addFrom(vals, 4));
						}
					}
				}
				
				return outX;
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return BLANK;
	}

	public static boolean notEmpty() {
		return !PROCESSES.isEmpty();
	}

	public static ArrayList<Process> get() {
		return (ArrayList<Process>) ((ArrayList<Process>) PROCESSES).clone();
	}

	public static LinkedList<Process> getProcesses(final String process) {
		final LinkedList<Process> out = new LinkedList<Process>();
		
		if (PROCESSES == null || PROCESSES.isEmpty()) {
			return out;
		}
		
		for (final Process processa : PROCESSES) {
			if (processa.getExecutable().equals(process) || processa.getExecutable().endsWith(process)) {
				out.add(processa);
			}
		}
		
		return out;
	}
	
	public static LinkedList<Process> getProcesses(final String... processes) {
		final LinkedList<Process> out = new LinkedList<Process>();
		
		if (PROCESSES == null || PROCESSES.isEmpty()) {
			return out;
		}
		
		for (final String process : processes) {
			for (final Process processa : PROCESSES) {
				if (processa.getExecutable().equals(process) || processa.getExecutable().endsWith(process)) {
					out.add(processa);
				}
			}
		}
		
		return out;
	}
	
	public static void main(String[] args) {
		final List<Process> processes = parse();
		for (final Process s : processes) {
			JVMUtil.async_println(s);
		}
	}
}
